package bufferpoll

import (
	"context"
	"distributed-cache/constant"
	"distributed-cache/percolator"
	"github.com/go-redis/redis/v8"
	"log"
	"strconv"
	"strings"
	"sync"
	"time"
)

var once sync.Once

type operationNode struct {
	GetOperation []string
	SetOperation chan string
	ResultMap    map[string]string
	RedisBean    *RedisProxy
	BitSet       *percolator.BitSet
	CacheTable   *percolator.CacheQueue
}

type VirtualPool struct {
	// 虚拟节点
	OperationNode []operationNode
	current       int
}

type Result struct {
	Flag  bool   `json:"flag"`
	Value string `json:"value"`
}

func (pool *VirtualPool) GetCurrentIndex() int {
	return pool.current
}

func (pool *VirtualPool) IncreaseCurrentIndex() {
	pool.current++
}

var virtualPool *VirtualPool

func GetVirtualPool(ctx context.Context) *VirtualPool {
	// 只会执行一次
	once.Do(func() {
		virtualPool = &VirtualPool{OperationNode: make([]operationNode, 1024)}
		for i := 0; i < len(virtualPool.OperationNode); i++ {
			// 初始化管道
			virtualPool.OperationNode[i].SetOperation = make(chan string, 10)
			// 初始化布隆过滤器
			virtualPool.OperationNode[i].BitSet = percolator.MakeBitSet()
			// 初始化缓存队列
			virtualPool.OperationNode[i].CacheTable = percolator.GetCacheQueue()
		}
		go BackRunInsert(ctx)
	})
	return virtualPool
}

func SetOperation(ctx context.Context, index int, cmd string, pool *VirtualPool) {
	pool.OperationNode[index].SetOperation <- cmd
}
func GetOperation(index int, cmd string, pool *VirtualPool) bool {
	virtualNode := pool.OperationNode[index]
	virtualNode.GetOperation = append(virtualNode.GetOperation, cmd)
	return true
}

func GetResult(ctx context.Context, index int, key string, pool *VirtualPool) *Result {
	if !pool.OperationNode[index].BitSet.Has(percolator.StringToHash(key)) {
		result := &Result{Flag: false}
		result.Value = "nonexistence"
		return result
	}
	key = strings.Join([]string{strconv.Itoa(index), key}, ":")
	cacheValue, has := pool.OperationNode[index].CacheTable.GetCache(key)
	if has {
		return &Result{Flag: has, Value: cacheValue}
	}
	originIndex := index

	result := &Result{Flag: false}
	for {
	LOOP:
		if index >= len(pool.OperationNode) {
			// 找到最后一位则从头来
			index = 0
		}
		virtualNode := pool.OperationNode[index]
		// 如果为空，则向后遍历
		if virtualNode.RedisBean == nil {
			index++
			continue
		}
		value, err := virtualNode.RedisBean.real.Get(ctx, key).Result()
		if err == redis.Nil {
			// 如果找不到，则判断此实例是否在迁移状态下，如果是，则从该实例之后继续遍历
			if virtualNode.RedisBean.status == constant.MIGRATION {
				index++
				goto LOOP
			}
			result.Value = "nonexistence"
		} else if err != nil {
			result.Value = "ExceptionOccurred "
		} else {
			result.Flag = true
			result.Value = value
		}
		pool.OperationNode[originIndex].CacheTable.Add(key, value)
		return result
	}
}
func SetResult(index int, key string, value string, pool *VirtualPool) bool {
	virtualNode := pool.OperationNode[index]
	virtualNode.ResultMap[key] = value
	return true
}

func BackRunInsert(ctx context.Context) {
	currentIndex := 0
	for {
		if currentIndex >= len(virtualPool.OperationNode) {
			time.Sleep(1)
			currentIndex = 0
		}
		operationNode := virtualPool.OperationNode[currentIndex]

		if len(operationNode.SetOperation) == 0 {
			currentIndex++
			continue
		}
		// 寻找当前节点以后有实例的虚拟节点进行插入
		insertIndex := currentIndex
		for ; insertIndex < len(virtualPool.OperationNode) && virtualPool.OperationNode[insertIndex].RedisBean == nil; insertIndex++ {
		}
		if insertIndex >= len(virtualPool.OperationNode) {
			insertIndex = 0
			for ; insertIndex < currentIndex && virtualPool.OperationNode[insertIndex].RedisBean == nil; insertIndex++ {
			}
		}
		if virtualPool.OperationNode[insertIndex].RedisBean == nil {
			panic("not redis bean")
		}
		prefix := strconv.Itoa(currentIndex)
		keyToValue := make([]interface{}, 0)
		data, ok := <-operationNode.SetOperation
		for ok {
			command := strings.Split(data, ":")
			operationNode.BitSet.Add(percolator.StringToHash(command[0]))
			operationNode.CacheTable.UpdateValue(command[0], command[1])
			keyToValue = append(keyToValue, strings.Join([]string{prefix, command[0]}, ":"))
			keyToValue = append(keyToValue, command[1])
			select {
			case data = <-operationNode.SetOperation:
				command := strings.Split(data, ":")
				keyToValue = append(keyToValue, strings.Join([]string{prefix, command[0]}, ":"))
				keyToValue = append(keyToValue, command[1])
			case <-time.After(time.Nanosecond):
				ok = false
			}
		}
		operationNode = virtualPool.OperationNode[insertIndex]
		if operationNode.RedisBean.real != nil {
			_, err := operationNode.RedisBean.real.MSet(ctx, keyToValue...).Result()
			if err != nil {
				log.Fatal("MSet err")
			}
		}
	}
}
